use std::time::Instant;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{sync_channel, SyncSender, Receiver};

use glam::{DVec3, dvec3};

mod math;
use math::*;

mod aabb;
use aabb::*;

mod bvh;
use bvh::*;

mod scene;
use scene::*;

mod ray;
use ray::*;

mod sphere;
use sphere::*;

mod quad;
use quad::*;

mod constant_medium;
use constant_medium::*;

mod material;
use material::*;

mod transform;
use transform::*;

mod texture;
use texture::*;

mod renderer;
use renderer::*;

mod demo;
use demo::*;

struct RTDemo {
    // the width of buffer
    w: usize,
    // the height of buffer
    h: usize,

    // sender from ray tracing threads to main thread
    tx: SyncSender<(usize, DVec3)>,
    // receiver in main thread, recv result from ray tracing threads
    rx: Receiver<(usize, DVec3)>,
    // exit flag
    exit: Arc<AtomicBool>,
    handle: Option<std::thread::JoinHandle<()>>,

    // the number of pixel has been rendered
    finish_num: usize,

    // time elapsed of ray tracing
    time: Instant,

    // the buffer for store rendered result, u32=A8R8G8B8
    buffer: Vec<u32>,
}

impl RTDemo {
    fn new(w: usize, h: usize) -> Self {
        let (tx, rx) = sync_channel(SCREEN_PIXEL_NUM);

        let mut buffer = vec![0; w * h];
        // draw red line in bottom of buffer
        {
            let idx = (h - 1) * w;
            let u32_red = Self::dvec3_to_u32(&dvec3(1.0, 0.0, 0.0));
            for i in 0..w {
                buffer[idx + i] = u32_red;
            }
        }

        Self {
            w,
            h,
            tx,
            rx,
            exit: Arc::new(AtomicBool::new(false)),
            handle: None,
            finish_num: 0,
            time: Instant::now(),
            buffer,
        }
    }

    fn on_start(&mut self) {
        // record start time
        self.time = Instant::now();

        let tx = self.tx.clone();
        let (renderer, hitable) = match DEMO_SWITCH {
            0 => demo1(tx),
            1 => demo2(tx),
            2 => demo3(tx),
            3 => demo4(tx),
            4 => demo5(tx),
            _ => panic!("invalid demo switch index"),
        };

        let mut hitables = Hitables::default();
        hitables.add_boxed(hitable);

        // start ray tracing thread, and keep it's handle
        let exit = self.exit.clone();

        let handle = std::thread::spawn(move || {
            renderer.render(hitables, exit);
        });
        self.handle = Some(handle);
    }

    fn on_update(&mut self) {
        // recv data from render threads
        let data: Vec<(usize, DVec3)> = self.rx
            .try_iter()
            .collect();

        // draw data to buffer
        for (idx, color) in &data {
            let x = idx % SCREEN_W;
            let y = idx / SCREEN_W;

            self.draw(x, y, &color);
        }

        // update finish num
        self.finish_num += data.len();

        // draw progress bar if data changed
        if !data.is_empty() {
           if self.finish_num == SCREEN_PIXEL_NUM {
               println!("rt time elapsed: {:?}", self.time.elapsed());
           }

           let progress = ((self.finish_num as f64 / SCREEN_PIXEL_NUM as f64) * F64_SCREEN_W) as usize;

           for x in 0..progress {
               self.draw(x, self.h - 1, &dvec3(0.0, 1.0, 0.0));
           }
        }
    }

    fn on_close(&mut self) {
        self.exit.store(true, Ordering::Relaxed);

        // wait ray tracing threads join
        if let Some(handle) = self.handle.take() {
            handle
                .join()
                .expect("wait ray tracing thread exit failed");
        }
    }

    /// draw color to buffer
    /// 
    #[inline]
    fn draw(&mut self, x: usize, y: usize, color: &DVec3) {
        self.buffer[y * self.w + x] = Self::dvec3_to_u32(color);
    }

    /// encode color to u32 value
    ///
    #[inline]
    fn dvec3_to_u32(v: &DVec3) -> u32 {
        let r = (256.0 * INTERVAL.clamp(v.x)) as u32;
        let g = (256.0 * INTERVAL.clamp(v.y)) as u32;
        let b = (256.0 * INTERVAL.clamp(v.z)) as u32;

        // alpha is 0xff
        (u32::MAX << 24) | (r << 16) | (g << 8) | b
    }
}

fn main() {
    use minifb::{Window, WindowOptions, Key};

    let w = SCREEN_W;
    // one pixel for ray trace progress on bottom of window
    let h = SCREEN_H + 1;

    let mut window = Window::new(
        "RT One Weekend", 
        w, 
        h, 
        WindowOptions::default(),
    )
    .expect("create window failed");

    let mut demo = RTDemo::new(w, h);

    demo.on_start();
    while window.is_open() && !window.is_key_down(Key::Escape) {
        demo.on_update();

        window.update_with_buffer(
            &demo.buffer,
            w,
            h,
        )
        .expect("update window with buffer failed");
    }
    demo.on_close();
}
